﻿using System;

using System.Net;
using System.Net.Sockets;

using System.Text;

using System.Diagnostics;

namespace UdpSyncEchoClient
{
    class Program
    {
        static IPEndPoint CreateEndPoint(string hostNameOrAddress, int port)
        {
            if (string.IsNullOrEmpty(hostNameOrAddress))
            {
                return new IPEndPoint(IPAddress.Any, port);
            }
            else
            {
                IPAddress address;
                if (IPAddress.TryParse(hostNameOrAddress, out address))
                    // Use the provided IP address.
                    return new IPEndPoint(address, port);
                else
                    // Exception will occur if DNS lookup fails.
                    return new IPEndPoint(Dns.GetHostEntry(hostNameOrAddress).AddressList[0], port);
            }
        }

        static void Main(string[] args)
        {
            // IPAddress.Parse can only accept numeric format, like "xxx.xxx.xxx.xxx"
            // address like "localhost" can only be used after being parsed by DNS
            IPEndPoint remoteEndpoint = CreateEndPoint("localhost", 6001);
            Socket udpSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            string strInput, strRecv;
            byte[] recvData = new byte[1024];
            int recvLength;
            EndPoint recvEndpoint = CreateEndPoint(string.Empty,0);

            while (true)
            {
                strInput = Console.ReadLine();
                if (strInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
                    break;

                /// <remark>
                /// when udp socket send datagram to a server not started yet
                /// "SendTo" will not throw any exception
                /// but "ReceiveFrom" will throw a "ConnectionReset" exception 
                /// !!!!!!!!!!!!!!!!!!
                /// this "ConnectionReset" exception is caused by "IMCP port unreachable"
                /// by the code like "Socket.IOControl(SIO_UDP_CONNRESET, optionInValue, optionOutValue);"
                /// the programmer can neglect this exception
                /// but I don't think neglect is a good choice.
                /// in my opinion, catch and do some further processment on this exception(such as logging)
                /// maybe a better choice
                /// </remark>
                try
                {
                    udpSock.SendTo(Encoding.ASCII.GetBytes(strInput), remoteEndpoint);

                    recvLength = udpSock.ReceiveFrom(recvData, ref recvEndpoint);
                    strRecv = Encoding.ASCII.GetString(recvData, 0, recvLength);
                }
                catch (SocketException ex)
                {
                    Console.WriteLine("!!! ERROR OCCURS with errcode=[{0},{1}]",ex.SocketErrorCode,ex.ErrorCode);
                    Console.WriteLine("{0}",ex.ToString());
                    break;
                }

                Debug.Assert(recvLength == strInput.Length);
                Debug.Assert(strInput == strRecv);
                Console.WriteLine("[SYNC CLIENT] Echo back: {0}", strRecv);
            }

            udpSock.Close();
            Console.WriteLine("socket is closed,program exits");
        }
    }
}
